001 /*
002 /*
003 * Copyright 2005 Stephen J. McConnell
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.
015 *
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 package net.dpml.library.info;
021
022 import java.io.File;
023 import java.io.IOException;
024 import java.io.FileNotFoundException;
025 import java.net.URI;
026 import java.net.URISyntaxException;
027 import java.util.List;
028 import java.util.ArrayList;
029 import java.util.Properties;
030
031 import net.dpml.library.Feature;
032 import net.dpml.library.TypeBuilder;
033 import net.dpml.library.info.ResourceDirective.Classifier;
034
035 import net.dpml.lang.Category;
036 import net.dpml.lang.Part;
037 import net.dpml.lang.Version;
038
039 import net.dpml.util.DecodingException;
040 import net.dpml.util.DOM3DocumentBuilder;
041 import net.dpml.util.ElementHelper;
042
043 import org.w3c.dom.Element;
044 import org.w3c.dom.Document;
045 import org.w3c.dom.TypeInfo;
046
047 /**
048 * Utility class used for construction of a module model from an XML source.
049 *
050 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
051 * @version 1.0.0
052 */
053 public final class LibraryDecoder extends LibraryConstants
054 {
055 private static final DOM3DocumentBuilder DOCUMENT_BUILDER = new DOM3DocumentBuilder();
056
057 /**
058 * Construct a library directive from XML source.
059 * @param source the XML source file
060 * @return the library directive
061 * @exception IOException if an IO exception occurs
062 */
063 public LibraryDirective build( File source ) throws IOException
064 {
065 if( null == source )
066 {
067 throw new NullPointerException( "source" );
068 }
069 if( !source.exists() )
070 {
071 throw new FileNotFoundException( source.toString() );
072 }
073 if( source.isDirectory() )
074 {
075 final String error =
076 "File ["
077 + source
078 + "] references a directory.";
079 throw new IllegalArgumentException( error );
080 }
081 try
082 {
083 final Element root = getRootElement( source );
084 File base = source.getParentFile();
085 return buildLibraryDirective( base, root );
086 }
087 catch( Exception e )
088 {
089 final String error =
090 "Unexpected error while attempting to load library."
091 + "File: '" + source + "'";
092 IOException ioe = new IOException( error );
093 ioe.initCause( e );
094 throw ioe;
095 }
096 }
097
098 /**
099 * Construct a resource directive from source.
100 * @param uri the source uri
101 * @return the resource directive
102 * @exception IOException if an IO exception occurs
103 */
104 public ResourceDirective buildResource( URI uri ) throws IOException
105 {
106 try
107 {
108 final Document document = DOCUMENT_BUILDER.parse( uri );
109 Element root = document.getDocumentElement();
110 return buildResourceDirectiveFromElement( null, root, null );
111 }
112 catch( Exception e )
113 {
114 final String error =
115 "Unexpected error while attempting to load module."
116 + "URI: '" + uri + "'";
117 IOException ioe = new IOException( error );
118 ioe.initCause( e );
119 throw ioe;
120 }
121 }
122
123 /**
124 * Resolve the root DOM element of the supplied file.
125 * @param source the source XML file
126 * @return the root element
127 * @exception IOException if an io error occurs
128 */
129 private Element getRootElement( File source ) throws IOException
130 {
131 File file = source.getCanonicalFile();
132 final Document document = DOCUMENT_BUILDER.parse( file.toURI() );
133 return document.getDocumentElement();
134 }
135
136 /**
137 * Build a library directive using an XML element.
138 * @param base the base directory
139 * @param element the module element
140 * @return the library directive
141 * @exception Exception if an exception occurs
142 */
143 private LibraryDirective buildLibraryDirective( File base, Element element ) throws Exception
144 {
145 final String elementName = element.getTagName();
146 if( !LIBRARY_ELEMENT_NAME.equals( elementName ) )
147 {
148 final String error =
149 "Element is not a library.";
150 throw new IllegalArgumentException( error );
151 }
152
153 // get type descriptors, modules and properties
154
155 Properties properties = null;
156 ImportDirective[] imports = new ImportDirective[0];
157 List list = new ArrayList();
158 Element[] children = ElementHelper.getChildren( element );
159 for( int i=0; i<children.length; i++ )
160 {
161 Element child = children[i];
162 final String tag = child.getTagName();
163 if( PROPERTIES_ELEMENT_NAME.equals( tag ) )
164 {
165 properties = buildProperties( child );
166 }
167 else if( IMPORTS_ELEMENT_NAME.equals( tag ) )
168 {
169 imports = buildImportDirectives( child );
170 }
171 else
172 {
173 ResourceDirective resource = buildResourceDirectiveFromElement( base, child, null );
174 list.add( resource );
175 }
176 }
177 ResourceDirective[] resources = (ResourceDirective[]) list.toArray( new ResourceDirective[0] );
178 return new LibraryDirective( imports, resources, properties );
179 }
180
181 /**
182 * Build a resource using an XML element.
183 * @param base the base directory
184 * @param element the module element
185 * @param offset the imported file directory offset
186 * @throws Exception if an error occurs
187 */
188 private ResourceDirective buildResourceDirectiveFromElement(
189 File base, Element element, String offset ) throws Exception
190 {
191 final String elementName = element.getTagName();
192 final String path = ElementHelper.getAttribute( element, "file" );
193 if( null != path )
194 {
195 try
196 {
197 File file = new File( base, path );
198 File source = file.getCanonicalFile();
199 if( !source.exists() )
200 {
201 final String error =
202 "Local file does not exist."
203 + "\n base: " + base
204 + "\n path: " + path
205 + "\n source: " + source;
206 throw new DecodingException( element, error );
207 }
208 else if( source.isDirectory() )
209 {
210 final String error =
211 "Local file references a directory."
212 + "\n base: " + base
213 + "\n path: " + path
214 + "\n source: " + source;
215 throw new DecodingException( element, error );
216 }
217 else
218 {
219 final File parent = source.getParentFile();
220 final Element root = getRootElement( source );
221 String basedir = getRelativePath( base, parent );
222 if( null != offset )
223 {
224 basedir = offset + "/" + basedir;
225 }
226 return buildResourceDirectiveFromElement( base, root, basedir );
227 }
228 }
229 catch( DecodingException e )
230 {
231 throw e;
232 }
233 catch( Throwable e )
234 {
235 final String error =
236 "Internal error while attempting to resolve a import directive.";
237 throw new DecodingException( element, error, e );
238 }
239 }
240 else
241 {
242 return buildResourceDirective( base, element, offset );
243 }
244 }
245
246 private ResourceDirective buildResourceDirective( File base, Element element ) throws Exception
247 {
248 return buildResourceDirective( base, element, null );
249 }
250
251 private ResourceDirective buildResourceDirective( File base, Element element, String path ) throws Exception
252 {
253 Classifier classifier = null;
254 final String tag = element.getTagName();
255 if( RESOURCE_ELEMENT_NAME.equals( tag ) || PROJECT_ELEMENT_NAME.equals( tag )
256 || MODULE_ELEMENT_NAME.equals( tag ) )
257 {
258 final String name = ElementHelper.getAttribute( element, "name" );
259 final String version = ElementHelper.getAttribute( element, "version" );
260 String basedir = ElementHelper.getAttribute( element, "basedir" );
261 if( path != null )
262 {
263 if( basedir == null )
264 {
265 basedir = path;
266 }
267 else
268 {
269 basedir = path + "/" + basedir;
270 }
271 }
272
273 if( PROJECT_ELEMENT_NAME.equals( tag ) )
274 {
275 classifier = Classifier.LOCAL;
276 if( null == basedir )
277 {
278 basedir = ".";
279 }
280 }
281 else if( MODULE_ELEMENT_NAME.equals( tag ) )
282 {
283 if( null != basedir )
284 {
285 classifier = Classifier.LOCAL;
286 }
287 else
288 {
289 classifier = Classifier.EXTERNAL;
290 }
291 }
292 else
293 {
294 classifier = Classifier.EXTERNAL;
295 }
296
297 final InfoDirective info =
298 buildInfoDirective(
299 ElementHelper.getChild( element, "info" ) );
300
301 final DataDirective[] data =
302 buildDataTypes(
303 ElementHelper.getChild( element, "types" ) );
304
305 final DependencyDirective[] dependencies =
306 buildDependencyDirectives(
307 ElementHelper.getChild( element, "dependencies" ) );
308
309 final FilterDirective[] filters =
310 buildFilterDirectives(
311 ElementHelper.getChild( element, "filters" ) );
312
313 final Properties properties =
314 buildProperties(
315 ElementHelper.getChild( element, "properties" ) );
316
317 if( MODULE_ELEMENT_NAME.equals( tag ) )
318 {
319 File anchor = getAnchorDirectory( base, basedir );
320 ArrayList list = new ArrayList();
321 Element[] children = ElementHelper.getChildren( element );
322 for( int i=0; i<children.length; i++ )
323 {
324 Element child = children[i];
325 final String t = child.getTagName();
326
327 if( RESOURCE_ELEMENT_NAME.equals( t )
328 || PROJECT_ELEMENT_NAME.equals( t )
329 || MODULE_ELEMENT_NAME.equals( t ) )
330 {
331 ResourceDirective directive =
332 buildResourceDirectiveFromElement( anchor, child, null );
333 list.add( directive );
334 }
335 }
336
337 ResourceDirective[] resources =
338 (ResourceDirective[]) list.toArray( new ResourceDirective[0] );
339 return ModuleDirective.createModuleDirective(
340 name, version, classifier, basedir, info, data, dependencies,
341 properties, filters, resources );
342 }
343 else
344 {
345 return ResourceDirective.createResourceDirective(
346 name, version, classifier, basedir, info, data, dependencies,
347 properties, filters );
348 }
349 }
350 else
351 {
352 final String error =
353 "Invalid element name ["
354 + tag
355 + "].";
356 throw new DecodingException( element, error );
357 }
358 }
359
360 private File getAnchorDirectory( File base, String path ) throws IOException
361 {
362 if( null == base )
363 {
364 return null;
365 }
366 if( !base.exists() )
367 {
368 final String error =
369 "Base directory [" + base + "] does not exist.";
370 throw new IllegalArgumentException( error );
371 }
372 if( null == path )
373 {
374 return base;
375 }
376 else
377 {
378 return new File( base, path ).getCanonicalFile();
379 }
380 }
381
382 private String getRelativePath( File base, File dir ) throws IOException
383 {
384 String baseSpec = base.getCanonicalPath();
385 String dirSpec = dir.getCanonicalPath();
386 if( dirSpec.equals( baseSpec ) )
387 {
388 return ".";
389 }
390 if( dirSpec.startsWith( baseSpec ) )
391 {
392 return dirSpec.substring( baseSpec.length() + 1 );
393 }
394 else
395 {
396 final String error =
397 "Supplied dir [" + dirSpec + "] is not with base [" + baseSpec + "].";
398 throw new IllegalArgumentException( error );
399 }
400 }
401
402 /**
403 * Build an array of include directives contained within the supplied enclosing element.
404 * @param element the enclosing element
405 * @return the array of includes
406 */
407 private ImportDirective[] buildImportDirectives( Element element ) throws DecodingException
408 {
409 Element[] children = ElementHelper.getChildren( element );
410 ImportDirective[] includes = new ImportDirective[ children.length ];
411 for( int i=0; i<children.length; i++ )
412 {
413 Element child = children[i];
414 includes[i] = buildImportDirective( child );
415 }
416 return includes;
417 }
418
419 private ImportDirective buildImportDirective( Element element ) throws DecodingException
420 {
421 final String tag = element.getTagName();
422 final Properties properties = buildProperties( element );
423 if( IMPORT_ELEMENT_NAME.equals( tag ) )
424 {
425 try
426 {
427 if( element.hasAttribute( "file" ) )
428 {
429 final String value = ElementHelper.getAttribute( element, "file", null );
430 return new ImportDirective( ImportDirective.FILE, value, properties );
431 }
432 else if( element.hasAttribute( "uri" ) )
433 {
434 final String value = ElementHelper.getAttribute( element, "uri", null );
435 return new ImportDirective( ImportDirective.URI, value, properties );
436 }
437 else
438 {
439 final String error =
440 "Import element does not declare a 'file' or 'uri' attribute.\n"
441 + element.toString();
442 throw new IllegalArgumentException( error );
443 }
444 }
445 catch( Throwable e )
446 {
447 final String error =
448 "Internal error while attempting to resolve an import directive.";
449 throw new DecodingException( element, error, e );
450 }
451 }
452 else
453 {
454 final String error =
455 "Invalid include element name ["
456 + tag
457 + "].";
458 throw new IllegalArgumentException( error );
459 }
460 }
461
462 private DependencyDirective[] buildDependencyDirectives( Element element ) throws DecodingException
463 {
464 if( null == element )
465 {
466 return new DependencyDirective[0];
467 }
468 else
469 {
470 final String tag = element.getTagName();
471 if( DEPENDENCIES_ELEMENT_NAME.equals( tag ) )
472 {
473 Element[] children = ElementHelper.getChildren( element );
474 DependencyDirective[] dependencies = new DependencyDirective[ children.length ];
475 for( int i=0; i<children.length; i++ )
476 {
477 Element child = children[i];
478 dependencies[i] = buildDependencyDirective( child );
479 }
480 return dependencies;
481 }
482 else
483 {
484 final String error =
485 "Invalid dependency element name ["
486 + tag
487 + "].";
488 throw new IllegalArgumentException( error );
489 }
490 }
491 }
492
493 private DependencyDirective buildDependencyDirective( Element element ) throws DecodingException
494 {
495 String name = element.getTagName();
496 Scope scope = Scope.parse( name );
497 if( Scope.BUILD.equals( scope ) )
498 {
499 IncludeDirective[] includes = buildIncludeDirectives( element, false );
500 return new DependencyDirective( Scope.BUILD, includes );
501 }
502 else if( Scope.RUNTIME.equals( scope ) )
503 {
504 IncludeDirective[] includes = buildIncludeDirectives( element, true );
505 return new DependencyDirective( Scope.RUNTIME, includes );
506 }
507 else
508 {
509 IncludeDirective[] includes = buildIncludeDirectives( element, false );
510 return new DependencyDirective( Scope.TEST, includes );
511 }
512 }
513
514 /**
515 * Build an array of include directives contained within the supplied enclosing element.
516 * @param element the enclosing element
517 * @param flag if category processing is required
518 * @return the array of includes
519 */
520 private IncludeDirective[] buildIncludeDirectives( Element element, boolean flag ) throws DecodingException
521 {
522 Element[] children = ElementHelper.getChildren( element );
523 IncludeDirective[] includes = new IncludeDirective[ children.length ];
524 for( int i=0; i<children.length; i++ )
525 {
526 Element child = children[i];
527 includes[i] = buildIncludeDirective( child, flag );
528 }
529 return includes;
530 }
531
532 private IncludeDirective buildIncludeDirective( Element element, boolean flag ) throws DecodingException
533 {
534 final String tag = element.getTagName();
535 final Properties properties = buildProperties( element );
536 if( INCLUDE_ELEMENT_NAME.equals( tag ) )
537 {
538 Category category = buildCategory( element, flag );
539 if( element.hasAttribute( "key" ) )
540 {
541 final String value = ElementHelper.getAttribute( element, "key", null );
542 return new IncludeDirective( IncludeDirective.KEY, category, value, properties );
543 }
544 else if( element.hasAttribute( "ref" ) )
545 {
546 final String value = ElementHelper.getAttribute( element, "ref", null );
547 return new IncludeDirective( IncludeDirective.REF, category, value, properties );
548 }
549 else if( element.hasAttribute( "uri" ) )
550 {
551 final String value = ElementHelper.getAttribute( element, "uri", null );
552 return new IncludeDirective( IncludeDirective.URI, category, value, properties );
553 }
554 else
555 {
556 final String error =
557 "Include directive does not declare a 'uri', 'key' or 'ref' attribute.\n"
558 + element.toString();
559 throw new IllegalArgumentException( error );
560 }
561 }
562 else
563 {
564 final String error =
565 "Invalid include element name ["
566 + tag
567 + "].";
568 throw new DecodingException( element, error );
569 }
570 }
571
572 private Category buildCategory( Element element, boolean flag )
573 {
574 if( !flag )
575 {
576 return Category.UNDEFINED;
577 }
578 else
579 {
580 final String value = ElementHelper.getAttribute( element, "tag", "private" );
581 return Category.parse( value );
582 }
583 }
584
585 private InfoDirective buildInfoDirective( Element element )
586 {
587 if( null == element )
588 {
589 return null;
590 }
591 else
592 {
593 String title = ElementHelper.getAttribute( element, "title" );
594 Element child = ElementHelper.getChild( element, "description" );
595 if( null == child )
596 {
597 return new InfoDirective( title, null );
598 }
599 else
600 {
601 String value = ElementHelper.getValue( child );
602 String description = trim( value );
603 return new InfoDirective( title, description );
604 }
605 }
606 }
607
608 private String trim( String value )
609 {
610 if( null == value )
611 {
612 return null;
613 }
614 String trimmed = value.trim();
615 if( trimmed.startsWith( "\n" ) )
616 {
617 return trim( trimmed.substring( 1 ) );
618 }
619 else if( trimmed.endsWith( "\n" ) )
620 {
621 return trim( trimmed.substring( 0, trimmed.length() - 1 ) );
622 }
623 else
624 {
625 return trimmed;
626 }
627 }
628
629 private FilterDirective[] buildFilterDirectives( Element element ) throws Exception
630 {
631 if( null == element )
632 {
633 return new FilterDirective[0];
634 }
635 else
636 {
637 Element[] children = ElementHelper.getChildren( element );
638 FilterDirective[] filters = new FilterDirective[ children.length ];
639 for( int i=0; i<children.length; i++ )
640 {
641 Element child = children[i];
642 String token = ElementHelper.getAttribute( child, "token" );
643 if( "filter".equals( child.getTagName() ) )
644 {
645 String value = ElementHelper.getAttribute( child, "value" );
646 filters[i] = new SimpleFilterDirective( token, value );
647 }
648 else if( "feature".equals( child.getTagName() ) )
649 {
650 String id = ElementHelper.getAttribute( child, "id" );
651 Feature feature = Feature.parse( id );
652 String ref = ElementHelper.getAttribute( child, "ref" );
653 String type = ElementHelper.getAttribute( child, "type" );
654 boolean alias = ElementHelper.getBooleanAttribute( child, "alias" );
655 filters[i] = new FeatureFilterDirective( token, ref, feature, type, alias );
656 }
657 else
658 {
659 final String error =
660 "Element name not recognized ["
661 + child.getTagName()
662 + "] (expecting 'filter').";
663 throw new DecodingException( element, error );
664 }
665 }
666 return filters;
667 }
668 }
669
670 private DataDirective[] buildDataTypes( Element element ) throws Exception
671 {
672 if( null == element )
673 {
674 return new DataDirective[0];
675 }
676 Element[] children = ElementHelper.getChildren( element );
677 DataDirective[] data = new DataDirective[ children.length ];
678 for( int i=0; i<children.length; i++ )
679 {
680 Element child = children[i];
681 data[i] = buildDataType( child );
682 }
683 return data;
684 }
685
686 private DataDirective buildDataType( Element element ) throws Exception
687 {
688 TypeInfo info = element.getSchemaTypeInfo();
689 String namespace = info.getTypeNamespace();
690 String typeName = info.getTypeName();
691
692 if( null == namespace )
693 {
694 throw new NullPointerException( "namespace" );
695 }
696
697 if( MODULE_XSD_URI.equals( namespace ) )
698 {
699 // it's a generic type declaration
700
701 final String id = getID( element );
702 final Properties properties = getProperties( element );
703 final Version version = getVersion( element );
704 return new TypeDirective( id, version, properties );
705 }
706 else if( info.isDerivedFrom( MODULE_XSD_URI, "AbstractType", TypeInfo.DERIVATION_EXTENSION ) )
707 {
708 if(
709 PART_XSD_URI.equals( namespace )
710 || info.isDerivedFrom( PART_XSD_URI, "StrategyType", TypeInfo.DERIVATION_EXTENSION ) )
711 {
712 final Version version = getVersion( element );
713 return new TypeDirective( element, "part", version );
714 }
715 else
716 {
717 // id attribute is required
718
719 final String id = getID( element );
720 final Version version = getVersion( element );
721 return new TypeDirective( element, id, version );
722 }
723 }
724 else
725 {
726 final String error =
727 "Unsupported element encountered during type directive production."
728 + "\nNamespace: "
729 + namespace
730 + "\nType: "
731 + info.getTypeName()
732 + "\nName: "
733 + element.getTagName();
734 throw new DecodingException( element, error );
735 }
736 }
737
738 private Version getVersion( Element element )
739 {
740 final String version = ElementHelper.getAttribute( element, "version" );
741 if( null != version )
742 {
743 return Version.parse( version );
744 }
745 else if( getAliasFlag( element ) )
746 {
747 return Version.NULL_VERSION;
748 }
749 else
750 {
751 return null;
752 }
753 }
754
755 private Properties buildProperties( Element element )
756 {
757 if( null == element )
758 {
759 return null;
760 }
761 Properties properties = new Properties();
762 Element[] children = ElementHelper.getChildren( element );
763 for( int i=0; i<children.length; i++ )
764 {
765 Element child = children[i];
766 String tag = child.getTagName();
767 if( "property".equals( tag ) )
768 {
769 String key = ElementHelper.getAttribute( child, "name", null );
770 if( null == key )
771 {
772 final String error =
773 "Property declaration does not contain a 'name' attribute.";
774 throw new IllegalArgumentException( error );
775 }
776 else
777 {
778 String value = ElementHelper.getAttribute( child, "value", null );
779 properties.setProperty( key, value );
780 }
781 }
782 }
783 return properties;
784 }
785
786 private URI getURIFromSpec( String spec )
787 {
788 if( null == spec )
789 {
790 return null;
791 }
792 else
793 {
794 try
795 {
796 return new URI( spec );
797 }
798 catch( URISyntaxException e )
799 {
800 final String error =
801 "Type descriptor uri ["
802 + spec
803 + "] could not be converted to a URI value.";
804 throw new IllegalArgumentException( error );
805 }
806 }
807 }
808
809 private TypeBuilder loadTypeBuilder( URI uri ) throws Exception
810 {
811 ClassLoader classloader = TypeBuilder.class.getClassLoader();
812 Object[] args = new Object[0];
813 Part part = Part.load( uri );
814 Object handler = part.instantiate( args );
815 if( handler instanceof TypeBuilder )
816 {
817 return (TypeBuilder) handler;
818 }
819 else
820 {
821 final String error =
822 "Plugin ["
823 + uri
824 + "] does not implement the "
825 + TypeBuilder.class.getName()
826 + " interface.";
827 throw new IllegalArgumentException( error );
828 }
829 }
830
831 /**
832 * Return the id attribute of the supplied element.
833 * @param element the DOM element
834 * @return the id value
835 * @exception DecodingException if an error occurs during element evaluation
836 */
837 protected String getID( Element element ) throws DecodingException
838 {
839 final String id = ElementHelper.getAttribute( element, "id" );
840 if( null == id )
841 {
842 final String error =
843 "Missing type 'id'.";
844 throw new DecodingException( element, error );
845 }
846 else
847 {
848 return id;
849 }
850 }
851
852 /**
853 * Return the alias attribute of the supplied element.
854 * @param element the DOM element
855 * @return the alias production flag value
856 */
857 protected boolean getAliasFlag( Element element )
858 {
859 return ElementHelper.getBooleanAttribute( element, "alias", false );
860 }
861
862 /**
863 * Return properties declared within the supplied element as immediate
864 * child <property> elements.
865 * @param element the enclosing DOM element
866 * @return the resolved properties instance
867 */
868 protected Properties getProperties( Element element )
869 {
870 Properties properties = new Properties();
871 Element[] children = ElementHelper.getChildren( element );
872 for( int i=0; i<children.length; i++ )
873 {
874 Element child = children[i];
875 String tag = child.getTagName();
876 if( "property".equals( tag ) )
877 {
878 String key = ElementHelper.getAttribute( child, "name", null );
879 if( null == key )
880 {
881 final String error =
882 "Property declaration does not contain a 'name' attribute.";
883 throw new IllegalArgumentException( error );
884 }
885 else
886 {
887 String value = ElementHelper.getAttribute( child, "value", null );
888 properties.setProperty( key, value );
889 }
890 }
891 }
892 return properties;
893 }
894 }